#!/usr/bin/env python3
template = """\

@Loader:
  GOSUB {lbl[DefFn]}
  IF FN W({sym[HIMEM]})<{sym[LastByte]} THEN PRINT "Not enough memory":STOP
  CLEAR 9,{sym[Start]}:BLOAD"{filename}"
  PRINT "Ready, press F5 to run tests":KEY 5,"\x15run"+CHR$(13)
@LastLoader:
  # The DELETE statement also stops the program, so we need the user to press F5
  DELETE {lbl[Loader]}-{lbl[LastLoader]}

  GOSUB {lbl[DefFn]}
  DEF USR={sym[Start]}
  S=0

@NextMode:
  SCREEN S:PRINT "Testing, please wait..."
  # Make all sprites visible
  FOR N=0 TO &H3E STEP 2:VPOKE N+N+&H1B00,N:VPOKE N+N+&H1B01,N*4:NEXT
  # Move sprite pattern table to 3000h, so we have at least some feedback
  VDP(6)=6
  E=USR(0)
  GOSUB {lbl[PrintResults]}
  S=S+1:IF S < 4 THEN {lbl[NextMode]}
  END

@PrintResults:
  SCREEN 1:WIDTH 32
  COLOR 15,4-2*(E<>0),4
  PRINT "Screen mode:";S
  PRINT "Error code (0=no error):";E
  PRINT "Cycles/frame:";FN Z({sym[CycFrm1]})
  PRINT "ACK works ";FN S({sym[AckAfterInt]});"cyc. after INT"
  PRINT "Bit 7 set ";FN S({sym[Bit7AfterInt]});"cyc. after INT"
  PRINT "ACK takes";FN S({sym[AckBetween1]});"to";\
FN S({sym[AckBetween2]});"cycles"
  PRINT "1st fast wr. failure cycles:"
  PRINT "12T:";FN Z({sym[FirstBad12]});" 14T:";FN Z({sym[FirstBad14]})
  PRINT "17T:";FN Z({sym[FirstBad17]});" 18T:";FN Z({sym[FirstBad18]})
  PRINT "19T:";FN Z({sym[FirstBad19]});" 20T:";FN Z({sym[FirstBad20]})
  PRINT "21T:";FN Z({sym[FirstBad21]});" 22T:";FN Z({sym[FirstBad22]})
  PRINT "23T:";FN Z({sym[FirstBad23]});" 24T:";FN Z({sym[FirstBad24]})
  IF S<>3 THEN PRINT "Press a key for next mode...";INPUT$(1)
  RETURN

@DefFn:
  DEFINT A-Y
  # Signed byte, note condition returns -1 if true
  DEF FN S(A)=PEEK(A)+256*(PEEK(A)>127)

  # Signed word
  DEF FN W(A)=PEEK(A)+256*FN S(A+1)
  DEF FN Z(A)=PEEK(A)+256*PEEK(A+1)+65536*PEEK(A+2)

  RETURN

@1000:
  GOSUB {lbl[DefFn]}
  FOR N=&H3000 TO &H3FFF STEP 2:IF VPEEK(N)=252 AND VPEEK(N+1)=254 THEN NEXT
  PRINT HEX$(N-&H3000)
  FOR N=N TO &H3FFE:U=VPEEK(N)
  IF U<>252 AND U<>254 AND U<>1 THEN PRINT HEX$(N),HEX$(U)
  NEXT
  FOR N=&H3000 TO &H3FFE:PRINT HEX$(N),HEX$(VPEEK(N)):NEXT
"""

fnames = {'cas':'CAS:VDPtst', 'dsk':'vdptest.bin'}

import sys, re
from asc2cld import tokenize, encode

class Val(object):
  def __init__(self, n):
    self.u = n & 0xFFFF
    self.s = self.u if self.u < 32768 else self.u - 65536
  def __str__(self):
    return str(self.s)  # return the signed one


def main():
  if len(sys.argv) < 2 or sys.argv[1].lower() not in fnames:
    sys.stderr.write("Usage: python3 loadertpl.py {cas|dsk}\n")

  parsesym = re.compile(r'^([A-Za-z_?@.][A-Za-z0-9_?@.$]*)'
    r'\s+EQU\s+0([0-9A-F]{4})H$')
  kind = sys.argv[1].lower()
  sym = {}
  f = open('vdptest%s.sym' % kind[0], 'r')
  try:
    for line in f:
      g = parsesym.search(line)
      if g:
        r = Val(int(g.group(2), 16))
        assert g.group(0) not in sym
        sym[g.group(1)] = r
  finally:
    f.close()

  fname = fnames[kind]

  # Deal with the line breaks as inserted by Python in this .py source file
  prg = template.replace('\r\n','\n').replace('\r','\n').split('\n')

  # Parse labels and add line numbers
  labels = {}
  offset = 0
  for idx in range(len(prg)):
    while idx < len(prg):
      linenum = (idx + 1) * 10 + offset
      line = prg[idx].lstrip(' ')
      if line == '' or line.startswith('#'):
        del prg[idx]
        continue
      if line.startswith('@'):
        if not line.endswith(':'):
          raise Exception('Incorrect label syntax (missing colon after label)')
        label = line[1:-1]
        if label.isnumeric():
          offset = int(label, 0) - (idx + 1) * 10
        else:
          labels[label] = linenum
        del prg[idx]
        continue
      prg[idx] = str(linenum) + line
      break

  prg = '\r\n'.join(prg)
  prg = prg.format(sym=sym, filename=fname, lbl=labels)

  if kind == 'cas':
    prg = b'\xFF' + tokenize(prg)
  else:
    prg = encode(prg)
  sys.stdout.buffer.write(prg)

main()
